using MongoDB.Driver;
using Sustainalytics.AwsS3.Client;
using Sustainalytics.DataLayer;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.Entities.Governance;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Utils.Configuration;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Sustainalytics.Governance.Reporting
{
    public class ReportDataProvider : IReportDataProvider
    {
        private readonly CancellationToken _token;
        private readonly int _bulkSize;
        private int _skip;
        private readonly string _dwConnectionString;
        private readonly string _inputFileconnectionString;
        private readonly string _bucketName;

        public ReportDataProvider(int bulkSize, CancellationToken token, string dwConnectionString, string inputFileconnectionString)
        {
            _token = token;
            _dwConnectionString = dwConnectionString;
            _inputFileconnectionString = inputFileconnectionString;
            _bulkSize = bulkSize;
            _skip = 0;
            _bucketName = SettingsHelper.Read("s3-governance-reports", "dev-globalaccess-europe/GovernanceReports");
        }

        public ReportDataSource[] GetCompanyReportData()
        {
            return GetCompanyReportData(Guid.Empty);
        }

        public ReportDataSource[] GetCompanyReportData(Guid companyId)
        {
            return GetNextData(companyId);
        }

        private ReportDataSource[] GetNextData(Guid companyId)
        {
            var batch = new ConcurrentBag<ReportDataSource>();

            List<GovernanceCompanyProfile> companyProfiles;

            if (companyId != Guid.Empty)
            {
                companyProfiles = MongoFactory.GetCollectionFromDatabase<GovernanceCompanyProfile>(_dwConnectionString)
                    .Find(x => x.Id == companyId)
                    .ToList();
            }
            else
            {
                companyProfiles = MongoFactory.GetCollectionFromDatabase<GovernanceCompanyProfile>(_dwConnectionString)
                    .Find(FilterDefinition<GovernanceCompanyProfile>.Empty)
                    .Skip(_skip)
                    .Limit(_bulkSize)
                    .ToList();
            }

            _skip += _bulkSize;

            if (!companyProfiles.Any()) return batch.ToArray();

            var maxDegreeOfParallelism = SettingsHelper.Read("MaxDegreeOfParallelism", 1);

            var options = new ParallelOptions()
            {
                CancellationToken = _token,
                MaxDegreeOfParallelism = maxDegreeOfParallelism
            };

            IDictionary<Guid, GovernanceComputationResult> computationResultsDictionary = null;
            IDictionary<Guid, List<GovernanceIssue>> issuesDictionary = null;
            IDictionary<Guid, List<GovernanceSource>> sourcesDictionary = null;
            IDictionary<Guid, byte[]> governanceDataProfileDictionary = null;
            IDictionary<Guid, byte[]> businessAndFinancialProfileDictionary = null;

            var companyIds = companyProfiles.Select(cp => cp.Id).ToList();

            var actions = new List<Action>
            {
                () => { computationResultsDictionary = GetComputationResults(companyIds); },
                () => { issuesDictionary = GetIssues(companyIds); },
                () => { sourcesDictionary = GetSources(companyIds); },
                //() => { governanceDataProfileDictionary = GetPdfFiles(companyIds, GovernancePdfType.GovernanceDataProfile); },
                //() => { businessAndFinancialProfileDictionary = GetPdfFiles(companyIds, GovernancePdfType.BusinessAndFinancialProfile); },
                () => { governanceDataProfileDictionary = GetPdfFilesFromS3(companyIds, GovernancePdfType.GovernanceDataProfile); },
                () => { businessAndFinancialProfileDictionary = GetPdfFilesFromS3(companyIds, GovernancePdfType.BusinessAndFinancialProfile); },
            };

            var exceptions = new ConcurrentQueue<Exception>();

            Parallel.ForEach(actions, options, action =>
            {
                try
                {
                    action();
                }
                catch (Exception ex)
                {
                    exceptions.Enqueue(ex);
                }
            });

            if (exceptions.Count > 0)
            {
                throw new AggregateException(exceptions);
            }

            exceptions = new ConcurrentQueue<Exception>();

            Parallel.ForEach(companyProfiles, options, companyProfile =>
            {
                try
                {
                    var reportDataSource = new ReportDataSource
                    {
                        CompanyId = companyProfile.Id,
                        CompanyProfile = companyProfile,

                        Issues = issuesDictionary.ContainsKey(companyProfile.Id)
                            ? issuesDictionary[companyProfile.Id]
                            : new List<GovernanceIssue>(),

                        Sources = sourcesDictionary.ContainsKey(companyProfile.Id)
                            ? sourcesDictionary[companyProfile.Id]
                            : new List<GovernanceSource>(),

                        GovernanceDataProfilePdf = governanceDataProfileDictionary.ContainsKey(companyProfile.Id)
                            ? governanceDataProfileDictionary[companyProfile.Id]
                            : null,

                        BusinessAndFinancialProfilePdf = businessAndFinancialProfileDictionary.ContainsKey(companyProfile.Id)
                            ? businessAndFinancialProfileDictionary[companyProfile.Id]
                            : null
                    };

                    var computationResult = computationResultsDictionary.ContainsKey(companyProfile.Id)
                        ? computationResultsDictionary[companyProfile.Id]
                        : null;

                    if (computationResult != null)
                    {
                        reportDataSource.OverallPerformance = computationResult.OverallPerformance;
                        reportDataSource.RelativePerformance = computationResult.RelativePerformance;
                        reportDataSource.IssuesPerformance = computationResult.IssuesPerformance;
                    }

                    batch.Add(reportDataSource);
                }
                catch (Exception ex)
                {
                    exceptions.Enqueue(ex);
                }
            });

            if (exceptions.Count > 0)
            {
                throw new AggregateException(exceptions);
            }

            return batch.ToArray();
        }

        private IDictionary<Guid, GovernanceComputationResult> GetComputationResults(List<Guid> companyIds)
        {
            var repository = new RepositoryCollection<GovernanceComputationResult>(_dwConnectionString);

            var list = repository.ReadAllWhere(x => companyIds.Contains(x.Id)).ToList();

            return list.ToDictionary(kv => kv.Id);
        }

        private IDictionary<Guid, List<GovernanceIssue>> GetIssues(IEnumerable<Guid> companyIds)
        {
            var repository = new RepositoryCollection<GovernanceIssue>(_dwConnectionString);

            var list = repository.ReadAllWhere(x => companyIds.Contains(x.CompanyId)).ToList();

            var groups = list.GroupBy(a => a.CompanyId);

            return groups.ToDictionary(kv => kv.Key, kv => kv.ToList());
        }

        private IDictionary<Guid, List<GovernanceSource>> GetSources(IEnumerable<Guid> companyIds)
        {
            var repository = new RepositoryCollection<GovernanceSource>(_dwConnectionString);

            var list = repository.ReadAllWhere(x => companyIds.Contains(x.CompanyId)).ToList();

            var groups = list.GroupBy(a => a.CompanyId);

            return groups.ToDictionary(kv => kv.Key, kv => kv.ToList());
        }

        private IDictionary<Guid, byte[]> GetPdfFiles(IEnumerable<Guid> companyIds, GovernancePdfType pdfType)
        {
            var repository = new RepositoryCollection<GovernancePdfProfileMetadata>(_inputFileconnectionString);

            var list = repository.ReadAllWhere(x => companyIds.Contains(x.CompanyId) &&
                x.GovernancePdfType == pdfType).ToList();

            var options = new ParallelOptions()
            {
                CancellationToken = _token,
                MaxDegreeOfParallelism = SettingsHelper.Read("MaxDegreeOfParallelism", 1)
            };

            var dictionary = new ConcurrentDictionary<Guid, byte[]>();

            Parallel.ForEach(list, options, profile =>
            {
                var fileRepository = new FileRepository(_inputFileconnectionString);
                var result = fileRepository.GetDownloadStream(profile.FileId);

                if (result == null || result.Item1 == null) return;

                var pdfFile = new byte[result.Item1.Length];

                result.Item1.Read(pdfFile, 0, (int)result.Item1.Length);

                dictionary.TryAdd(profile.CompanyId, pdfFile);
            });

            return dictionary.ToDictionary(kv => kv.Key, kv => kv.Value);
        }

        private IDictionary<Guid, byte[]> GetPdfFilesFromS3(IEnumerable<Guid> companyIds, GovernancePdfType pdfType)
        {
            var repository = new RepositoryCollection<GovernancePdfProfileMetadata>(_inputFileconnectionString);

            var list = repository.ReadAllWhere(x => companyIds.Contains(x.CompanyId) &&
                x.GovernancePdfType == pdfType).ToList();

            var options = new ParallelOptions()
            {
                CancellationToken = _token,
                MaxDegreeOfParallelism = SettingsHelper.Read("MaxDegreeOfParallelism", 1)
            };

            var dictionary = new ConcurrentDictionary<Guid, byte[]>();

            Parallel.ForEach(list, options, profile =>
            {
                var fileContentByteArray = LoadFileFromS3(profile.CompanyId, pdfType.ToString());
                if(fileContentByteArray != null)
                    dictionary.TryAdd(profile.CompanyId, fileContentByteArray);
            });

            return dictionary.ToDictionary(kv => kv.Key, kv => kv.Value);
        }

        public byte[] LoadFileFromS3(Guid companyId, string governancePdfType)
        {
            byte[] fileContentByteArray = null;
            S3ClientSettings _s3ClientSettings;
            _s3ClientSettings = new S3ClientSettings
            {
                AccessKey = SettingsHelper.Read("accesKeyId", "AKIAJKGZZMOFAVOVLHJA"),
                SecretAccessKey = SettingsHelper.Read("awsSecretKey", "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"),
                ServiceEndPoint = SettingsHelper.Read("s3ServiceEndPoint", "https://s3-eu-west-1.amazonaws.com/")
            };

            try
            {
                using (var s3Client = new S3Client(_s3ClientSettings))
                {
                    var fullPath = string.Format("{0}/{1}", governancePdfType, companyId.ToString() + ".pdf");
                    var stream = s3Client.GetFileStream(_bucketName, fullPath);
                    if (stream != null)
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            stream.CopyTo(memoryStream);
                            fileContentByteArray = memoryStream.ToArray();
                        }
                    }
                }
            }
            catch (Exception) { };
            return fileContentByteArray;
        }
    }
}
